use gs1::{checksum::gs1_checksum, epc};
use hex;

use wasm_bindgen::prelude::*;

// When `wee_alloc` feature is enabled, use `wee_alloc` as global allocator
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

struct GTIN {
    pub company: u64,
    pub company_digits: usize,
    pub item: u64,
    pub indicator: u8,
}

impl GTIN {
    fn from_epc(epc_string: &str) -> Option<GTIN> {
        let epc_binary = &hex::decode(epc_string).ok()?;
        let result = epc::decode_binary(epc_binary).ok()?;

        match result.get_value() {
            epc::EPCValue::SGTIN96(v) => Some(GTIN {
                company: v.gtin.company,
                company_digits: v.gtin.company_digits,
                item: v.gtin.item,
                indicator: v.gtin.indicator,
            }),
            _ => None,
        }
    }
}

fn epc2ean(epc_string: &str) -> Option<String> {
    let gtin = GTIN::from_epc(epc_string)?;
    let mut company = gtin.company.to_string();
    let mut item = gtin.item.to_string();

    company.truncate(gtin.company_digits);
    item.truncate(12 - gtin.company_digits);

    let s_upc = format!("{}{}", company, item);
    let checksum = gs1_checksum(&s_upc);

    Some(format!("{}{}{}", company, item, checksum))
}

// gets a string of EPCs separated by separator
// and returns a string of EANs separated by separator
#[wasm_bindgen]
pub fn batch_epc2ean(epc_strings: &str, separator: &str) -> String {
    epc_strings
        .split(separator)
        .map(|s| epc2ean(s))
        .filter(|r| r.is_some())
        .map(|r| r.unwrap())
        .collect::<Vec<String>>()
        .join(separator)
}

#[test]
fn test_epc2ean() {
    // valid EPCs will return an EAN like Some(String("40012121212"))
    let test_ean = epc2ean("3034F44BF043E6800000D970");
    assert!(test_ean.is_some());
    assert_eq!(test_ean.unwrap(), "4002556695309".to_string());

    // invalid EPCs will return None
    assert!(epc2ean("E28068900000000294BC7601").is_none());
    assert!(epc2ean("siebdruck").is_none());
}

#[test]
fn test_batch_epc2ean() {
    // valid EPCs will return EANs as String separated by separator
    let test_eans = batch_epc2ean("3034F44BF043E6800000D970,3034F457D8374EC000009B82", ",");
    assert_eq!(test_eans, "4002556695309,4003318566356".to_string());

    // invalid EPCs will return empty strings
    assert_eq!(batch_epc2ean("siebdruck", ","), "".to_owned());

    // mixed valid and invalid EPCs will only return results for the valid ones
    let test_mixed_eans = batch_epc2ean(
        "E28068900000000294BC7601,siebdruck,3034F44BF043E6800000D970",
        ",",
    );
    assert_eq!(test_mixed_eans, "4002556695309".to_string());
}
